﻿using MiPWinRTSDK.Interfaces;
using MiPWinRTSDK.MiP.Models;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using System.Threading.Tasks;
using Windows.ApplicationModel.Core;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Devices.Enumeration;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Core;

namespace MiPWinRTSDK
{
    class MiPBase : INotifyPropertyChanged, IMiPBase
    {
        public DeviceInformation Device { get; set; }

        #region Properties
        private byte chestLEDTimeOff;
        private byte chestLEDTimeOn;
        private Color chestLEDColor;
        public Color ChestLEDColor
        {
            get
            {
                return chestLEDColor;
            }
            private set
            {
                chestLEDColor = value;
                RaisePropertyChanged();
            }
        }
        public byte ChestLEDTimeOn
        {
            get
            {
                return chestLEDTimeOn;
            }
            private set
            {
                chestLEDTimeOn = value;
                RaisePropertyChanged();
            }
        }
        public byte ChestLEDTimeOff
        {
            get
            {
                return chestLEDTimeOff;
            }
            private set
            {
                chestLEDTimeOff = value;
                RaisePropertyChanged();
            }
        }
        #endregion




        public IAsyncAction DistanceDrive(DirectionEnum direction, byte distance, TurnEnum turn, int angle)
        {
            List<byte> bytes = new List<byte>();

            bytes.Add((byte)direction);
            bytes.Add(distance);
            bytes.Add((byte)turn);
            byte highbyte = (byte)((angle >> 8) & 0xff);
            byte lowbyte = (byte)(angle & 0xff);
            bytes.Add(highbyte);
            bytes.Add(lowbyte);
            return SendCommand(0x70, bytes).AsAsyncAction();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="speed">Speed (0~30)</param>
        /// <param name="timeInMiliseconds">Time in 7ms intervals (0~255) </param>
        /// <returns></returns>
        public IAsyncAction DriveForwardWithTime(byte speed, byte timeInMiliseconds)
        {
            List<byte> bytes = new List<byte>();
            byte time = (byte)(timeInMiliseconds);
            bytes.Add(speed);
            bytes.Add(time);
            return SendCommand(0x71, bytes).AsAsyncAction();
        }

        public IAsyncAction DriveBackwardWithTime(byte speed, byte timeInMiliseconds)
        {
            List<byte> bytes = new List<byte>();
            byte time = (byte)(timeInMiliseconds / 7);
            bytes.Add(speed);
            bytes.Add(time);
            return SendCommand(0x72, bytes).AsAsyncAction();
        }


        public IAsyncAction TurnLeftByAngle(byte speed, int angle)
        {
            List<byte> bytes = new List<byte>();
            bytes.Add((byte)(angle / 5));
            bytes.Add(speed);
            return SendCommand(0x73, bytes).AsAsyncAction();
        }
        public IAsyncAction TurnRightByAngle(byte speed, int angle)
        {
            List<byte> bytes = new List<byte>();
            bytes.Add((byte)(angle / 5));
            bytes.Add(speed);
            return SendCommand(0x74, bytes).AsAsyncAction();
        }

        public IAsyncAction ContinuousDrive(byte direction, byte spin)
        {
            List<byte> bytes = new List<byte>();
            bytes.Add(direction);
            bytes.Add(spin);
            return SendCommand(0x78, bytes).AsAsyncAction();
        }

        public IAsyncAction DelayTimeBetweenTwoClaps(int delay)
        {
            List<byte> bytes = new List<byte>();
            byte highbyte = (byte)((delay >> 8) & 0xff);
            byte lowbyte = (byte)(delay & 0xff);
            return SendCommand(0x20, bytes).AsAsyncAction();
        }

        internal async Task SendCommand(byte command, List<byte> commandBytes)
        {
            //if (this.Device== BluetoothConnectionStatus.Connected)
            //{
            var serviceGuid = Guid.Parse("0000ffe5-0000-1000-8000-00805f9b34fb");
            var characteristicGuid = Guid.Parse("0000ffe9-0000-1000-8000-00805f9b34fb");

            //Add command to the beginning
            commandBytes.Insert(0, command);
            await this.Device.WriteValueAsync(serviceGuid, characteristicGuid, commandBytes.ToArray());
            //}
        }

        public IAsyncAction RequestChestLED()
        {
            List<byte> bytes = new List<byte>();
            return SendCommand(0x83, bytes).AsAsyncAction();
        }


        public IAsyncAction SetChestLED(byte red, byte green, byte blue)
        {
            List<byte> bytes = new List<byte>();
            bytes.Add((byte)red);
            bytes.Add((byte)green);
            bytes.Add((byte)blue);
            return SendCommand(0x84, bytes).AsAsyncAction();
        }


        public IAsyncAction FlashChestLED(byte red, byte green, byte blue, byte timeOn, byte timeOff)
        {
            List<byte> bytes = new List<byte>();
            bytes.Add((byte)red);
            bytes.Add((byte)green);
            bytes.Add((byte)blue);
            bytes.Add((byte)timeOn);
            bytes.Add((byte)timeOff);
            return SendCommand(0x89, bytes).AsAsyncAction();
        }


        public IAsyncAction SetHeadLED(LightEnum light1, LightEnum light2, LightEnum light3, LightEnum light4)
        {
            List<byte> bytes = new List<byte>();
            bytes.Add((byte)light1);
            bytes.Add((byte)light2);
            bytes.Add((byte)light3);
            bytes.Add((byte)light4);
            return SendCommand(0x8a, bytes).AsAsyncAction();
        }


        public IAsyncAction RequestHeadLED()
        {
            List<byte> bytes = new List<byte>();
            return SendCommand(0x8b, bytes).AsAsyncAction();
        }

        public IAsyncAction SetUserData(byte address, byte data)
        {
            List<byte> bytes = new List<byte>();
            bytes.Add(address);
            bytes.Add(data);
            return SendCommand(0x12, bytes).AsAsyncAction();
        }

        public IAsyncAction GetUserOrOtherEepromData(byte address)
        {
            List<byte> bytes = new List<byte>();
            bytes.Add((byte)address);
            return SendCommand(0x13, bytes).AsAsyncAction();
        }

        public IAsyncAction GetHardwareInfo()
        {
            List<byte> bytes = new List<byte>();
            return SendCommand(0x19, bytes).AsAsyncAction();
        }

        public IAsyncAction GetVolume()
        {
            List<byte> bytes = new List<byte>();
            return SendCommand(0x16, bytes).AsAsyncAction();
        }

        public IAsyncAction PlaySound([ReadOnlyArray()] MiPSound[] sounds, byte numberOfTimesToRepeat)
        {
            List<byte> bytes = new List<byte>();
            foreach (var s in sounds)
            {
                if (s.Volume == null)
                    bytes.Add(s.Sound.Value);
                else
                    bytes.Add((byte)(s.Volume.Value + 0xf7));
            }
            bytes.Add(numberOfTimesToRepeat);
            return SendCommand(0x06, bytes).AsAsyncAction();
        }

        public IAsyncAction Sleep()
        {
            List<byte> bytes = new List<byte>();
            return SendCommand(0xFA, bytes).AsAsyncAction();
        }

        public IAsyncAction DisconnectApp()
        {
            List<byte> bytes = new List<byte>();
            return SendCommand(0xFE, bytes).AsAsyncAction();
        }
        public IAsyncAction Stop()
        {
            List<byte> bytes = new List<byte>();
            return SendCommand(0x77, bytes).AsAsyncAction();
        }


        public IAsyncAction RequestStatus()
        {
            List<byte> bytes = new List<byte>();
            return SendCommand(0x79, bytes).AsAsyncAction();
        }


        #region Recieve
        GattCharacteristic CharacteristicsNotify { get; set; }
        
        public async void Connect()
        {
            try
            {
                var serviceGuid = Guid.Parse("0000ffe0-0000-1000-8000-00805f9b34fb");
                var characteristicGuid = Guid.Parse("0000ffe4-0000-1000-8000-00805f9b34fb");
                CharacteristicsNotify = await Device.SetUpNotifyAsync(serviceGuid, characteristicGuid);
                CharacteristicsNotify.ValueChanged += CharacteristicsNotify_ValueChanged;
            }
            catch { }
        }

        internal virtual void CharacteristicsNotify_ValueChanged(Windows.Devices.Bluetooth.GenericAttributeProfile.GattCharacteristic sender, Windows.Devices.Bluetooth.GenericAttributeProfile.GattValueChangedEventArgs args)
        {
            var data = args.CharacteristicValue.ToArray();
            var hexstring = UTF8Encoding.UTF8.GetString(data, 0, data.Count());
            var bytes = StringToByteArray(hexstring);

            switch (bytes[0])
            {
                //case 0x82: //GetCurrentMIPGameMode
                //    GameMode = (GameModeEmum)Enum.Parse(typeof(GameModeEmum), bytes[1].ToString());
                //    OnGameModeRecieved(GameMode);
                //    break;
                //case 0x03: //Receive IR Dongle code
                //    OnIRDongleCodeRecievedEvent(bytes.ToList());
                //    break;
                //case 0x79: //Request MIP status  
                //    BatteryLevel = bytes[1];
                //    Status = (StatusEnum)Enum.Parse(typeof(StatusEnum), bytes[2].ToString());
                //    OnMiPStatusRecieved(new MiPStatus() { BatteryLevel = BatteryLevel, Status = Status });
                //    break;
                //case 0x81: //Request weight update
                //    Weight = bytes[1];
                //    OnWeightUpdateRecieved(Weight);
                //    break;
                case 0x83: //RequestChestLED
                    ChestLEDColor = Color.FromArgb(255, bytes[1], bytes[2], bytes[3]);
                    if (bytes.Count() >= 5)
                    {
                        ChestLEDTimeOn = bytes[4];
                    }
                    if (bytes.Count() == 6)
                    {
                        ChestLEDTimeOff = bytes[5];
                    }

                    OnChestLEDRecieved(new ChestLED() { Color = ChestLEDColor, TimeOn = ChestLEDTimeOn, TimeOff = ChestLEDTimeOff });
                    break;
                //case 0x8b:// RequestHeadLED
                //    Light1 = (LightEnum)Enum.Parse(typeof(LightEnum), bytes[1].ToString());
                //    Light2 = (LightEnum)Enum.Parse(typeof(LightEnum), bytes[2].ToString());
                //    Light3 = (LightEnum)Enum.Parse(typeof(LightEnum), bytes[3].ToString());
                //    Light4 = (LightEnum)Enum.Parse(typeof(LightEnum), bytes[4].ToString());
                //    OnHeadLEDRecieved(new HeadLED() { Light1 = Light1, Light2 = Light2, Light3 = Light3, Light4 = Light4 });
                //    break;
                //case 0x0A: //GestureDetect
                //    var detectedGesture = (GestureEnum)Enum.Parse(typeof(GestureEnum), bytes[1].ToString());
                //    OnGestureRecieved(detectedGesture);
                //    break;
                //case 0x0D: //RadarMode
                //    RadarMode = (GestureRadarEnum)Enum.Parse(typeof(GestureRadarEnum), bytes[1].ToString());
                //    OnRadarModeRecieved(RadarMode);
                //    break;
                //case 0x0C: //Radar Response
                //    byte response = bytes[1];
                //    OnRadarRecieved(response);
                //    break;
                //case 0x0F: //Mip Detection Status
                //    MiPDetectionMode = bytes[1];
                //    MiPDetectionIRTxPower = bytes[2];
                //    OnMiPDetectionStatusRecieved(new MiPDetectionStatus() { Mode = MiPDetectionMode, IRTxPower = MiPDetectionIRTxPower });
                //    break;
                //case 0x04: //Mip Detected  
                //    var mipsettingNumber = bytes[1];
                //    OnMipDetectedRecieved(mipsettingNumber);
                //    break;
                case 0x1A: //Shake Detected 
                    OnShakeDetectedRecieved();
                    break;
                    //case 0x11: //IR Control Status 
                    //    IRControlStatus = bytes[1];
                    //    OnIRControlStatusRecieved(IRControlStatus);
                    //    break;
                    //case 0xFA: //Sleep
                    //    OnSleepRecieved();
                    //    break;
                    //case 0x13: //MIP User Or Other Eeprom Data
                    //    var address = bytes[1];
                    //    var userEepromdata = bytes[2];
                    //    OnEePromDataRecieved(new EepromData() { Address = address, Data = userEepromdata });
                    //    break;
                    //case 0x14: //Mip Software Version 
                    //    MiPVersion = string.Format("{0}.{1}.{2}.{3}", Convert.ToInt32(bytes[1]), Convert.ToInt32(bytes[2]), Convert.ToInt32(bytes[3]), Convert.ToInt32(bytes[4]));
                    //    OnSoftwareVersionRecieved(MiPVersion);
                    //    break;
                    //case 0x19: //Mip Hardware Info 
                    //    VoiceChipVersion = bytes[1];
                    //    HardwareVersion = bytes[2];
                    //    OnMiPHardwareVersionRecieved(new MiPHardwareVersion() { VoiceChipVersion = VoiceChipVersion, HardwareVersion = HardwareVersion });
                    //    break;
                    //case 0x16: //GetVolume
                    //    Volume = bytes[1];
                    //    OnVolumeRecieved(Volume);
                    //    break;
                    //case 0x1d: //Clap times
                    //    var times = bytes[1];
                    //    OnClapTimesRecieved(times);
                    //    break;
                    //case 0x1f: //Clap Status  
                    //    var isEnabled = bytes[1] == 0x00 ? false : true;
                    //    int delayTime = (bytes[2] << 8) & bytes[3];
                    //    OnClapStatusRecieved(new ClapStatus() { Enabled = isEnabled, DelayTime = delayTime });
                    //    break;
                    //default:
                    //    ValueRecieved(bytes);
                    //    break;
            }
        }

        public byte[] StringToByteArray(string hex)
        {
            return Enumerable.Range(0, hex.Length)
                             .Where(x => x % 2 == 0)
                             .Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
                             .ToArray();
        }
        #endregion

        #region Events    
        public event EventHandler<object> ShakeDetectedRecievedEvent;
        private void OnShakeDetectedRecieved()
        {
            ShakeDetectedRecievedEvent?.Invoke(this, null);
        }

        public event EventHandler<ChestLED> ChestLEDRecievedEvent;
        public event PropertyChangedEventHandler PropertyChanged;

        private void OnChestLEDRecieved(ChestLED chestLED)
        {
            ChestLEDRecievedEvent?.Invoke(this, chestLED);
        }
        #endregion

        internal void RaisePropertyChanged([CallerMemberName] string caller = "")
        {
            if (PropertyChanged != null)
            {
                CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
                () =>
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(caller));
                });
            }
        }
    }
}
